from coin.exchange.base.order_gateway import OrderSide
from coin.exchange.okex_futures.types import OkexFuturesOrderSide


def get_order_sign(order_side):
  if isinstance(order_side, OrderSide):
    if order_side == OrderSide.BUY:
      return 1
    elif order_side == OrderSide.SELL:
      return -1
  elif isinstance(order_side, OkexFuturesOrderSide):
    if order_side in [OkexFuturesOrderSide.BUY_OPEN, OkexFuturesOrderSide.BUY_CLOSE]:
      return 1
    elif order_side in [OkexFuturesOrderSide.SELL_OPEN, OkexFuturesOrderSide.SELL_CLOSE]:
      return -1
  raise ValueError(order_side)


class MmPricer(object):
  def __init__(self, *, product, true_book_qty, true_book_working_range_bps, tick_size):
    self.product = product
    self.true_book_qty = true_book_qty
    self.true_book_working_range_bps = true_book_working_range_bps
    self.tick_size = tick_size

  def get_best_working_orders(self, working_orders):
    mybid0_order = None
    myask0_order = None
    for working_order in working_orders:
      if working_order.product != self.product:
        continue
      order_sign = get_order_sign(working_order.order_side)
      if order_sign == 1:
        if mybid0_order is None or mybid0_order.price < working_order.price:
          mybid0_order = working_order
      elif order_sign == -1:
        if myask0_order is None or myask0_order.price > working_order.price:
          myask0_order = working_order
      else:
        raise ValueError(order_sign)
    return (mybid0_order, myask0_order)

  # important! should maintain only 1 orders for each side in this logic.
  # otherwise it will become headache.
  # TODO(jshin): Q. is it 1 side 1 order only? better to be.
  def get_nonself_truebook_impl(self, book, *, mybid0_order, myask0_order):
    truebid0p_selfincl = book.get_bid_price_by_qty(self.true_book_qty)
    if (mybid0_order is not None and truebid0p_selfincl <= mybid0_order.price):
      tb_adjust_buy_qty = self.true_book_qty + mybid0_order.qty
      truebid0p = book.get_bid_price_by_qty(tb_adjust_buy_qty)
    else:
      truebid0p = truebid0p_selfincl

    trueask0p_selfincl = book.get_ask_price_by_qty(self.true_book_qty)
    if (myask0_order is not None and trueask0p_selfincl >= myask0_order.price):
      tb_adjust_sell_qty = self.true_book_qty + myask0_order.qty
      trueask0p = book.get_ask_price_by_qty(tb_adjust_sell_qty)
    else:
      trueask0p = trueask0p_selfincl

    return (truebid0p, trueask0p)

  def get_nonself_bbo_impl(self, book, *, mybid0_order, myask0_order):
    mybid0p = (mybid0_order is not None) and mybid0_order.price
    myask0p = (myask0_order is not None) and myask0_order.price
    mybid0q = (mybid0_order is not None) and mybid0_order.qty
    myask0q = (myask0_order is not None) and myask0_order.qty
    nsbid0p = None
    nsask0p = None
    for price, entry in book.iteritems_bid():
      if price != mybid0p or entry.qty > mybid0q:
        nsbid0p = price
        break
    for price, entry in book.iteritems_ask():
      if price != myask0p or entry.qty > myask0q:
        nsask0p = price
        break
    return (nsbid0p, nsask0p)

  def get_nonself_truebook(self, book, order_gateway):
    mybid0_order, myask0_order = self.get_best_working_orders(order_gateway.get_working_order())
    return self.get_nonself_truebook_impl(book,
                                          mybid0_order=mybid0_order,
                                          myask0_order=myask0_order)

  def get_nonself_bbo(self, book, order_gateway):
    mybid0_order, myask0_order = self.get_best_working_orders(order_gateway.get_working_order())
    return self.get_nonself_bbo_impl(book, mybid0_order=mybid0_order, myask0_order=myask0_order)

  def get_truebook_posting_range(self, book, order_gateway):
    truebid0p, trueask0p = self.get_nonself_truebook(book, order_gateway)
    bid0p_max = (truebid0p is not None) and (truebid0p *
                                             (1 + self.true_book_working_range_bps * 1e-4))
    ask0p_min = (trueask0p is not None) and (trueask0p *
                                             (1 - self.true_book_working_range_bps * 1e-4))
    return (bid0p_max, ask0p_min)

  def get_bbo_posting_range(self, book, order_gateway):
    nsbid0p, nsask0p = self.get_nonself_bbo(book, order_gateway)
    if nsbid0p is not None:
      bid0p_max = min(nsbid0p + self.tick_size, book.ask0().price - self.tick_size)
    else:
      bid0p_max = None
    if nsask0p is not None:
      ask0p_min = max(nsask0p - self.tick_size, book.bid0().price + self.tick_size)
    else:
      ask0p_min = None

    return (bid0p_max, ask0p_min)

  def apply_or_not_none(self, func, lhs, rhs):
    if lhs is None:
      return None
    elif rhs is None:
      return lhs
    else:
      return func(lhs, rhs)

  # Q: not really beautiful? handling all this None's?
  def get_post_price(self, book, order_gateway, buy_price_before_adj, sell_price_before_adj):
    bid0p_max1, ask0p_min1 = self.get_truebook_posting_range(book, order_gateway)
    bid0p_max2, ask0p_min2 = self.get_bbo_posting_range(book, order_gateway)

    buy_price = self.apply_or_not_none(
        min, self.apply_or_not_none(min, buy_price_before_adj, bid0p_max1), bid0p_max2)

    sell_price = self.apply_or_not_none(
        max, self.apply_or_not_none(max, sell_price_before_adj, ask0p_min1), ask0p_min2)

    if buy_price is not None and sell_price is not None and buy_price == sell_price:
      buy_price = buy_price - self.tick_size
      sell_price = sell_price + self.tick_size

    return (buy_price, sell_price)
